Know which consumers will break before you rotate a signing key, change an issuer, or drop a claim.
Keyway derives your JWT consumer inventory automatically — from Istio, Envoy, Kubernetes, OIDC discovery and a shipped library-behaviour database — and verifies it with real tokens against real staging endpoints using 13 purpose-built probes. It versions the derived contract, diffs it on every change, classifies each change as widened or narrowed, and answers the one question that actually matters before a key rotation:
"If I rotate this key, who breaks, and how long is the safe grace period?"
Keyway never mutates your configuration, never blocks a deploy, and never asks you to author a model file. If a feature needs you to describe your own system, it is out of scope by definition.
Key rotations, issuer migrations and claim removals cause outages because nobody has an
accurate, current map of who validates what. That map is normally tribal knowledge that
rots. Keyway rebuilds it on every run and, crucially, proves it by minting synthetic
tokens (expired, wrong-issuer, alg=none, tampered, canary, …) and watching how each
consumer responds.
| Capability | How |
|---|---|
| Auto-discovers consumers | Istio RequestAuthentication, Envoy jwt_authn, K8s projected SA tokens, OIDC/Keycloak client registry |
| Verifies with real tokens | 13 probes (valid, expired, wrong-issuer/audience, alg=none, alg-confusion, tampered, missing-claim, retired-key, canary, header-bypass, …), staging-only, with a hard production guard |
| Versions & diffs the contract | Canonical SHA-256 hash; identical systems produce identical hashes; a first run establishes a baseline with zero alerts |
| Classifies changes | widened / narrowed / neutral / unknown, with severity |
| Answers blast radius | keyway blast-radius rotate-key --issuer … --kid … → who breaks, who's ready, recommended grace period, bounding consumer |
| Runs a canary key | Announces a key in JWKS without signing, then measures which consumers pick it up |
| Web dashboard | React + TypeScript UI over the HTTP API |
Try it — zero config. Runs the app and the embedded web UI on an in-memory store; no database, no cluster required.
make demo # build + run on http://localhost:8080
# or, prebuilt (published on each release):
docker run -p 8080:8080 ghcr.io/keyway-ai/keywayOpen http://localhost:8080. The UI loads on built-in sample data, so you can
explore findings, coverage, blast radius and the agent inspector right away. (To
drive the live API instead of sample data, set KEYWAY_API_TOKEN and connect from
the UI's Settings.)
Run it for real. Point Keyway at your own configs and a Postgres store:
# 1. Bring up Postgres (+ a reference Keycloak) for local dev, and point Keyway at it
make dev-up
export KEYWAY_DB_URL=postgres://keyway:keyway@localhost:5432/keyway?sslmode=disable
# 2. Build the binaries
make build
# 3. Register your issuers
./bin/keyway init
./bin/keyway issuer add --type keycloak --url https://kc.example.com/realms/main \
--admin-credential-env KC_ADMIN
# 4. Discover consumers and snapshot the contract (first run = baseline, zero alerts)
./bin/keyway discover --namespace default
./bin/keyway snapshot
# 5. Model a rotation
./bin/keyway blast-radius rotate-key --issuer keycloak-main --kid rsa-2026-01Example output (issuer/kid are placeholders):
Rotating rsa-2026-01 on keycloak-main affects 47 consumers.
WILL BREAK (3)
payments-api 48h JWKS cache, RefreshUnknownKID=false [probe:canary_key #8812]
owner: team-payments
legacy-reporting no JWKS refresh configured [lib:keyfunc v1.9.0]
owner: team-data
mobile-gateway cached key pinned in config [istio:RequestAuthentication/mobile-gw]
owner: team-mobile
READY (41) run with --verbose to list
UNKNOWN (3) insufficient evidence — not probeable
RECOMMENDED GRACE PERIOD: 9d 6h
bound by payments-api (48h cache, measured 6d4h to pick up canary, x1.5 margin)
NOTE: 3 consumers unknown — treat as a lower bound.
The single binary serves the UI at /. For frontend work, the Vite dev server
runs standalone on sample data — no backend needed:
make demo # the app + embedded UI on :8080 (in-memory store)
make serve # the API + scheduler on :8080 (uses your KEYWAY_DB_URL)
make web-dev # Vite dev server on :5173, sample data + hot reload (proxies /v1 → :8080)Gate pull requests on token-contract drift with the CLI or the GitHub Action —
against Keyway Cloud (hosted or your own keyway-cloud) for shared history, or
fully offline against a committed baseline (no account, no network):
- uses: Keyway-AI/keyway@v0
with:
server: https://cloud.example.com # omit for offline mode
token: ${{ secrets.KEYWAY_TOKEN }}
project: ${{ vars.KEYWAY_PROJECT }}
path: deploy/
fail-on: highkeyway cloud analyze --path deploy/ --baseline .keyway/baseline.json --fail-on highSee docs/ci.md for both modes, all flags, and token setup.
discovery ──┐
├─▶ contract build ─▶ hash/version ─▶ diff ─▶ classify ─▶ notify
issuers ───┤ │ ▲
│ ▼ │
libdefaults │ probe engine (13 probes) ────────────────┘
│ │
└──────────┴─▶ blast radius + grace period ─▶ CLI / HTTP API / Web UI
See ARCHITECTURE.md for the full guide — the layering, the
data flow, the extension seams, and where to change things — and
docs/architecture-review.md for the independent
design critique.
On a corpus of 1,226 realistic before/after changes (half real contract changes, half ordinary redeploy noise), Keyway catches 100% of real changes with 0% false alarms on the gated corpus — including a "noisy redeploy" that churns six unrelated things at once. Because a perfect score on a self-authored corpus proves consistency, not generalisation, it also survives mutation testing (24 mutants, 100% killed) and scores a deliberately honest 0.75 Youden on a held-out adversarial corpus. See BENCHMARK.md for the plain-English study, docs/benchmark-integrity.md for the overfit stress-tests, and reproduce it yourself:
make bench # scorecard
make bench-report # + an interactive report.htmlThe approach, benchmark design, and honest results — with limitations — are
written up as a whitepaper and a series of arXiv-style
research notes (contract discovery, drift classification,
adversarial verification, agent-auth). The on-site hub is at /research.
And it's validated against real, documented incidents — alg=none
(CVE-2022-23540), RS256→HS256
confusion (CVE-2022-23541), and
the JWKS key-rotation outage from
openfga/openfga#3099. Keyway
detects 8 of 8 — see docs/realworld-validation.md:
make validate # reproduce each cited incident and check Keyway flags itAnd the live-probe layer is scored end-to-end against real containerized
services — one secure, one per weakness (alg=none, RS256→HS256 confusion,
unverified signature, missing aud/iss/exp, header trust). Keyway returns
the correct verdict on 8/8 (100%):
make bench-l2 # docker-compose rig + Keyway's real probe engine (bench/l2)CI fails the build if accuracy drops below the PRD §13.4 thresholds or if Keyway stops detecting any documented real-world risk.
Keyway is actively developed. Milestones follow §15 of the PRD (M1–M9); the build
tracker lives in docs/progress.md and the open-items register
in docs/known-issues.md.
Contributions are welcome — start with CONTRIBUTING.md. Keyway ships a
400-scenario benchmark harness (bench/) that gates accuracy in CI, so new discovery/probe/diff
logic is expected to keep the scorecard above the §13.4 thresholds.
- Where we're headed: ROADMAP.md — closing named threat-coverage gaps is the top priority, and those make great first contributions.
- Get help / ask questions: SUPPORT.md and Discussions.
- How the project runs: GOVERNANCE.md · Code of Conduct.
- Good first issues: the
good first issueandhelp wantedlabels.
Keyway mints synthetic tokens and talks to auth infrastructure. Please read
SECURITY.md before running it, and never point it at production without the
explicit --i-know-this-is-production flag. Report vulnerabilities per SECURITY.md.
We hold Keyway to a documented secure-development process — SAST (CodeQL, gosec), DAST (OWASP ZAP against the demo), dependency + secret scanning (govulncheck, gitleaks, Trivy, Dependabot), SBOMs and signed release images. See docs/security/ssdlc.md.